#pragma once
#ifdef _WIN32

#define WINVER 0x0501

#include <ios>
#include <iostream>
#include <sstream>
#include <string>
#include <winsock2.h>
#include <ws2tcpip.h>

namespace krryn{
namespace system_imp{
	class winsock{
	public:
		static void create(){
			static winsock *l_Winsock = 0;

			if(!l_Winsock)
				l_Winsock = new winsock();
		}
	private:
		winsock(){
			// cleanup only if startup didn't fail
			m_Cleanup = (NO_ERROR == ::WSAStartup(MAKEWORD(2, 2), &m_Data));
		}

		~winsock(){
			if(m_Cleanup)
				::WSACleanup();
		}

		WSADATA m_Data;
		bool m_Cleanup;
	};

	template<typename E, typename T = std::char_traits<E> >
	class basic_inetworkbuf : public std::basic_stringbuf<E, T>{
	public:
		basic_inetworkbuf(unsigned short a_Port)
			: std::basic_stringbuf<E, T>(std::ios_base::in | std::ios_base::binary){

			winsock::create();
			m_ClientSocket = INVALID_SOCKET;
			if(!connect(a_Port)){
				// error
			}
		}

		~basic_inetworkbuf(){
			disconnect();
		}
	protected:
		typename T::int_type underflow(){
			std::basic_stringbuf<E, T>::underflow();
			if (this->gptr() < this->egptr()) return *(this->gptr());

			if(m_ClientSocket == INVALID_SOCKET){
				m_ClientSocket = accept(m_ListeningSocket, 0, 0);
			}

			int l_Size = recv(m_ClientSocket, m_Buffer, 512, 0);

			if(l_Size == 0){
				::shutdown(m_ClientSocket, SD_SEND);
				::closesocket(m_ClientSocket);

				m_ClientSocket = INVALID_SOCKET;

				return T::eof();
			}else if(l_Size > 0){
				this->str(std::basic_string<E, T>(m_Buffer, l_Size));
			}else{
				return T::eof();
			}

			return *(this->gptr());
		}
	private:
		char m_Buffer[512];

		bool connect(unsigned short a_Port){
			addrinfo *l_Result, l_Hints;

			::memset(&l_Hints, 0, sizeof(addrinfo));

			l_Hints.ai_family	= AF_INET;
			l_Hints.ai_socktype	= SOCK_STREAM;
			l_Hints.ai_protocol	= IPPROTO_TCP;
			l_Hints.ai_flags	= AI_PASSIVE;

			std::stringstream ss;
			ss << a_Port;

			int result = ::getaddrinfo(NULL, ss.str().c_str(), &l_Hints, &l_Result);

			if(result != 0){
				return false;
			}

			m_ListeningSocket = socket(l_Result->ai_family, l_Result->ai_socktype, l_Result->ai_protocol);

			if(m_ListeningSocket == INVALID_SOCKET){
				return false;
			}

			result = bind(m_ListeningSocket, l_Result->ai_addr, (int)l_Result->ai_addrlen);

			if(result == SOCKET_ERROR){
				return false;
			}

			result = listen(m_ListeningSocket, SOMAXCONN);

			if(result == SOCKET_ERROR){
				return false;
			}

			return true;
		}

		void disconnect(){
			::shutdown(m_ListeningSocket, SD_SEND);
			::closesocket(m_ListeningSocket);
		}

		SOCKET m_ListeningSocket;
		SOCKET m_ClientSocket;
	};

	template<typename E, typename T = std::char_traits<E> >
	class basic_onetworkbuf : public std::basic_stringbuf<E, T>{
	public:
		basic_onetworkbuf(const std::string &a_Ip, unsigned short a_Port)
			: std::basic_stringbuf<E, T>(std::ios_base::out | std::ios_base::binary){

			winsock::create();

			if(!connect(a_Ip.c_str(), a_Port)){
				// error
			}
		}

		~basic_onetworkbuf(){
			disconnect();
		}
	protected:
		int sync(){
			// send the data
			const std::string &l_String = this->str();
			::send(m_Socket, l_String.c_str(), l_String.length(), 0);

			// clear the buffer
			str(std::basic_string<E, T>());
			return 0;
		}
	private:
		bool connect(const std::string &a_IP, unsigned short a_Port){
			m_Socket = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

			sockaddr_in l_Address;

			l_Address.sin_family		= AF_INET;
			l_Address.sin_addr.s_addr	= inet_addr(a_IP.c_str());
			l_Address.sin_port			= htons(a_Port);

			int l_Result = ::connect(m_Socket, (SOCKADDR*) &l_Address, sizeof(l_Address));

			return l_Result != SOCKET_ERROR;
		}

		void disconnect(){
			::shutdown(m_Socket, SD_SEND);
			::closesocket(m_Socket);
		}

		SOCKET m_Socket;
	};
}
}

#endif
